package tps12.axwax.client.presenter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import tps12.axwax.client.LoginClient;
import tps12.axwax.client.LoginClientImpl;
import tps12.axwax.client.PuzzleClient;
import tps12.axwax.client.LoginClientImpl.LoginResults;
import tps12.axwax.client.PuzzleClient.BarredRectangularGrid;
import tps12.axwax.client.PuzzleClient.Cell;
import tps12.axwax.client.PuzzleClient.Clue;
import tps12.axwax.client.PuzzleClient.Puzzle;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;

public class SolvePuzzlePresenter implements Presenter {
	public interface GridDisplay extends HasClickHandlers {
		void setCellAppearance(int x, int y, boolean left, boolean top, int number);
		int[] getClickedCell(ClickEvent event);
		void setCellSelected(int x, int y, boolean light, boolean selected);
		void setCellValue(int x, int y, char c);
		void setLetters(String value);
		String getLetters();
	}

	public interface Display extends HasKeyPressHandlers, HasKeyDownHandlers {
		void setHeader(String title, String instructions);
		void addClues(String label, Collection<String> clues);
		GridDisplay createGrid(int width, int height);
		Widget asWidget();
		void setLoginUrl(String url);
		void setLogoutUrl(String url);
		void setCommStatus(boolean good);
		void setUser(String name);
	}
	
	public SolvePuzzlePresenter(String url, String letters, LoginClient login, PuzzleClient client, Display display) {
		this.url = url;
		this.letters = letters;
		this.login = login;
		this.client = client;
		this.display = display;
	}
	
	String url;
	String letters;
	boolean loggedIn = false;
	String loginUrl = "";
	LoginClient login;
	PuzzleClient client;
	Display display;

	@Override
	public void go(HasWidgets container) {
		container.clear();
		container.add(display.asWidget());
		loginUrl = Window.Location.getHref();
		int i = loginUrl.indexOf(url);
		if (i >= 0)
			loginUrl = loginUrl.substring(0, i) + url;
		getLogin(loginUrl, true);
	}
	
	static String clueString(Clue c) {
		return (c.number > 0 ? c.number + ". " : "") +
			c.text +
			(!c.format.equals("") ? " (" + c.format + ")" : "");
	}
	
	static Collection<String> clueStrings(Collection<Clue> clues) {
		List<String> s = new ArrayList<String>();
		for (Clue c : clues)
			s.add(clueString(c));
		return s;
	}
	
	static class PuzzleState {
		public boolean lightDown = false;
		public int[] selected = { -1, -1 };
	}
	
	void getLogin(String url, final boolean loadPuzzle) {
		login.login(url, new AsyncCallback<LoginClientImpl.LoginResults>() {
			@Override
			public void onFailure(Throwable caught) {
				GWT.log("Get login info failed", caught);
			}

			@Override
			public void onSuccess(LoginResults result) {
				if (result.loggedIn) {
					display.setUser(result.name);
					display.setLogoutUrl(result.url);
					loggedIn = true;
				}
				else
					display.setLoginUrl(result.url);
				if (loadPuzzle)
					getPuzzle();
			}
		});
	}
	
	void getPuzzle() {
		client.getPuzzle(url, new AsyncCallback<Puzzle>() {
			@Override
			public void onFailure(Throwable caught) {
				GWT.log("Get puzzle " + url + " failed", caught);
			}

			@Override
			public void onSuccess(final Puzzle result) {
				display.setHeader(result.title, result.instructions);
				
				final GridDisplay grid = display.createGrid(result.grid.width, result.grid.height);
				if (loggedIn)
					client.getPuzzleLetters(result.lettersUrl, new AsyncCallback<String>() {
						@Override
						public void onFailure(Throwable caught) {
							GWT.log("Get letters failed", caught);
						}
	
						@Override
						public void onSuccess(String result) {
							String union = "";
							for (int i = 0; i < result.length(); i++) {
								if (i < letters.length()) {
									char c = letters.charAt(i);
									if (c != ' ') {
										union += c;
										continue;
									}
								}
								union += result.charAt(i);
							}
							grid.setLetters(union);
						}
					});
				else
					grid.setLetters(letters);
				final PuzzleState state = new PuzzleState();
				int y = 0;
				for (Collection<Cell> r : result.grid.rows) {
					int x = 0;
					for (Cell c : r) {
						grid.setCellAppearance(x, y, c.leftBar, c.topBar, c.number);
						x++;
					}
					y++;
				}
				grid.addClickHandler(new ClickHandler() {
					@Override
					public void onClick(ClickEvent event) {
						int[] target = grid.getClickedCell(event);
						
						// if current selection clicked, switch light direction
						if (state.selected[0] == target[0] && state.selected[1] == target[1])
							state.lightDown = !state.lightDown;
						
						gridClickHandler(result.grid, grid, state, target);
						
						state.selected[0] = target[0];
						state.selected[1] = target[1];
					}
				});
				display.addKeyPressHandler(new KeyPressHandler() {
					@Override
					public void onKeyPress(KeyPressEvent event) {
						if (state.selected[0] > -1 && state.selected[1] > -1)
							keyPressHandler(result, grid, state, event.getCharCode());
					}
				});
				display.addKeyDownHandler(new KeyDownHandler() {
					@Override
					public void onKeyDown(KeyDownEvent event) {
						if (state.selected[0] > -1 && state.selected[1] > -1)
							keyDownHandler(result, grid, state, event);
					}
				});
				
				display.addClues("Across", clueStrings(result.across));
				display.addClues("Down", clueStrings(result.down));
			}	
		});
	}
	
	void setLetter(Puzzle puzzle, GridDisplay grid,
			int x, int y, char c) {
		grid.setCellValue(x, y, c);
		if (loggedIn)
			client.putPuzzleLetters(puzzle.lettersUrl, grid.getLetters(),
					new AsyncCallback<Boolean>() {
						@Override
						public void onFailure(Throwable caught) {
							display.setCommStatus(false);
						}

						@Override
						public void onSuccess(Boolean result) {
							display.setCommStatus(result.booleanValue());
						}
			});
		else
			// set the login url to save progress if user logs in
			getLogin(loginUrl + "&letters=" + grid.getLetters(), false);
	}

	void keyDownHandler(Puzzle puzzle,
			GridDisplay grid, PuzzleState state,
			KeyDownEvent event) {
		if (state.selected[0] > -1 && state.selected[1] > -1) {
			
			boolean arrow = true;
			if (event.isLeftArrow()) {
				if (state.selected[0] == 0)
					state.selected[0] = puzzle.grid.width-1;
				else
					state.selected[0]--;
			}
			else if (event.isRightArrow()) {
				if (state.selected[0] == puzzle.grid.width-1)
					state.selected[0] = 0;
				else
					state.selected[0]++;
			}
			else if (event.isUpArrow()) {
				if (state.selected[1] == 0)
					state.selected[1] = puzzle.grid.height-1;
				else
					state.selected[1]--;
			}
			else if (event.isDownArrow()) {
				if (state.selected[1] == puzzle.grid.height-1)
					state.selected[1] = 0;
				else
					state.selected[1]++;
			}
			else if (event.getNativeKeyCode() == KeyCodes.KEY_DELETE) {
				arrow = false;
				setLetter(puzzle, grid, state.selected[0], state.selected[1], ' ');
			}
			else return;
		
			int[] lightLimits = 
				setLightLimits(puzzle.grid, state.lightDown, state.selected);
			if (arrow) {
				// rotate direction if no room
				if (lightLimits[0] == lightLimits[1]) {
					state.lightDown = !state.lightDown;
					lightLimits = setLightLimits(puzzle.grid, state.lightDown, state.selected);
				}
			}
			else
				advanceSelection(state, lightLimits);
			highlightCells(puzzle.grid, grid,
					state.lightDown, state.selected, lightLimits);
		}
	}

	void keyPressHandler(Puzzle puzzle, final GridDisplay grid,
			final PuzzleState state, char c) {
		if (Character.isLetter(c)) {
			setLetter(puzzle, grid, state.selected[0], state.selected[1],
					Character.toUpperCase(c));
			int[] lightLimits = 
				setLightLimits(puzzle.grid, state.lightDown, state.selected);
			advanceSelection(state, lightLimits);
			highlightCells(puzzle.grid, grid,
					state.lightDown, state.selected, lightLimits);
		}
	}

	static void advanceSelection(final PuzzleState state, int[] lightLimits) {
		if (state.lightDown) {
			if (state.selected[1] < lightLimits[1])
				state.selected[1]++;
			else
				state.selected[1] = lightLimits[0];
		}
		else {
			if (state.selected[0] < lightLimits[1])
				state.selected[0]++;
			else
				state.selected[0] = lightLimits[0];
		}
	}

	static void gridClickHandler(BarredRectangularGrid puzzle,
			GridDisplay grid, PuzzleState state, int[] target) {
		// find light
		int[] lightLimits = setLightLimits(puzzle, state.lightDown, target);
		
		// rotate direction if no room
		if (lightLimits[0] == lightLimits[1]) {
			state.lightDown = !state.lightDown;
			lightLimits = setLightLimits(puzzle, state.lightDown, target);
		}
		
		highlightCells(puzzle, grid, state.lightDown, target, lightLimits);
	}

	static void highlightCells(BarredRectangularGrid puzzle, GridDisplay grid,
			boolean lightDown, int[] target, int[] lightLimits) {
		// highlight correct cells
		for (int x = 0; x < puzzle.width; x++)
			for (int y = 0; y < puzzle.height; y++) {
				boolean select = x == target[0] && y == target[1]; 
				boolean light = lightDown ?
						x == target[0] && y >= lightLimits[0] && y <= lightLimits[1] :
						y == target[1] && x >= lightLimits[0] && x <= lightLimits[1];
				grid.setCellSelected(x, y, light, select);
			}
	}
	
	static int[] setAcrossLimits(BarredRectangularGrid puzzle, int[] target) {
		int[] lightLimits = { Integer.MAX_VALUE, Integer.MIN_VALUE };

		int x = target[0], y = target[1];
		for (int i = x; i >= 0; i--)
			if (i == 0 || puzzle.rows.get(y).get(i).leftBar) {
				lightLimits[0] = i;
				break;
			}
		for (int i = x; i < puzzle.rows.get(y).size(); i++)
			if (i == puzzle.rows.get(y).size()-1 || puzzle.rows.get(y).get(i+1).leftBar) {
				lightLimits[1] = i;
				break;
			}
		if (lightLimits[0] == Integer.MAX_VALUE)
			lightLimits[0] = 0;
		if (lightLimits[1] == Integer.MIN_VALUE)
			lightLimits[1] = puzzle.rows.get(y).size();
		
		return lightLimits;
	}

	
	static int[] setDownLimits(BarredRectangularGrid puzzle, int[] target) {
		int[] lightLimits = { Integer.MAX_VALUE, Integer.MIN_VALUE };

		int x = target[0], y = target[1];
		for (int i = y; i >= 0; i--)
			if (i == 0 || puzzle.rows.get(i).get(x).topBar) {
				lightLimits[0] = i;
				break;
			}
		for (int i = y; i < puzzle.rows.size(); i++)
			if (i == puzzle.rows.size()-1 || puzzle.rows.get(i+1).get(x).topBar) {
				lightLimits[1] = i;
				break;
			}
		if (lightLimits[0] == Integer.MAX_VALUE)
			lightLimits[0] = 0;
		if (lightLimits[1] == Integer.MIN_VALUE)
			lightLimits[1] = puzzle.rows.size();
		
		return lightLimits;
	}

	static int[] setLightLimits(BarredRectangularGrid puzzle, boolean lightDown,
			int[] target) {
		return lightDown ? setDownLimits(puzzle, target) : setAcrossLimits(puzzle, target);
	}
}